package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	//Golang goroutine channel 实现并发和并行
	//为什么要使用goroutine呢
	//需求：要统计1-10000000的数字中那些是素数，并打印这些素数？
	//
	//素数：就是除了1和它本身不能被其他数整除的数
	//
	//实现方法：
	//
	//传统方法，通过一个for循环判断各个数是不是素数
	//使用并发或者并行的方式，将统计素数的任务分配给多个goroutine去完成，这个时候就用到了goroutine
	//goroutine 结合 channel

	//进程、线程以及并行、并发
	//进程
	//进程（Process）就是程序在操作系统中的一次执行过程，是系统进行资源分配和调度的基本单位，
	//进程是一个动态概念，是程序在执行过程中分配和管理资源的基本单位，每一个进程都有一个自己的地址空间。
	//一个进程至少有5种基本状态，它们是：初始态，执行态，等待状态，就绪状态，终止状态。
	//
	//通俗的讲进程就是一个正在进行执行的程序。
	//线程
	//线程是进程的一个执行实例，是程序执行的最小单元，它是比进程更小的能独立运行的基本单位
	//
	//一个进程可以创建多个线程，同一个进程中多个线程可以并发执行 ，一个线程要运行的话，至少有一个进程

	//并发和并行
	//并发：多个线程同时竞争一个位置，竞争到的才可以执行，每一个时间段只有一个线程在执行。
	//
	//并行：多个线程可以同时执行，每一个时间段，可以有多个线程同时执行。
	//
	//通俗的讲多线程程序在单核CPU上面运行就是并发，多线程程序在多核CUP上运行就是并行，如果线程数大于CPU核数，则多线程程序在多个CPU上面运行既有并行又有并发

	//Golang中协程（goroutine）以及主线程
	//golang中的主线程：（可以理解为线程/也可以理解为进程），在一个Golang程序的主线程上可以起多个协程。Golang中多协程可以实现并行或者并发。
	//
	//协程：可以理解为用户级线程，这是对内核透明的，也就是系统并不知道有协程的存在，
	//是完全由用户自己的程序进行调度的。Golang的一大特色就是从语言层面原生持协程，
	//在函数或者方法前面加go关键字就可创建一个协程。可以说Golang中的协程就是goroutine。

	//多协程和多线程
	//多协程和多线程：Golang中每个goroutine（协程）默认占用内存远比Java、C的线程少。
	//
	//OS线程（操作系统线程）一般都有固定的栈内存（通常为2MB左右），一个goroutine（协程）占用内存非常小，只有2KB左右，多协程goroutine切换调度开销方面远比线程要少。
	//
	//这也是为什么越来越多的大公司使用Golang的原因之一。
	//goroutine的使用以及sync.WaitGroup
	//并行执行需求
	//在主线程（可以理解成进程）中，开启一个goroutine，该协程每隔50毫秒秒输出“你好golang"
	//
	//在主线程中也每隔50毫秒输出“你好golang"，输出10次后，退出程序，要求主线程和goroutine同时执行。
	//
	//这是时候，我们就可以开启协程来了，通过 go关键字开启
	//主线程同时也输出相同的循环
	//wg.Add(1)
	//go test2()
	//wg.Add(1)
	//go test3()
	//for i := 0; i < 10; i++ {
	//	fmt.Println("main 主线程输出的 你好golang")
	//	time.Sleep(50 * time.Millisecond)
	//}
	//wg.Wait()
	//fmt.Println("主进程结束")
	//我们能够看到他们之间不存在所谓的顺序关系了
	//main 主线程输出的 你好golang
	//test 协程输出的 你好golang
	//main 主线程输出的 你好golang
	//test 协程输出的 你好golang
	//test 协程输出的 你好golang
	//main 主线程输出的 你好golang
	//main 主线程输出的 你好golang
	//test 协程输出的 你好golang
	//test 协程输出的 你好golang
	//main 主线程输出的 你好golang
	//但是上述的代码其实还有问题的，也就是说当主进程执行完毕后，不管协程有没有执行完成，都会退出
	//这是使用我们就需要用到 sync.WaitGroup等待协程
	//首先我们需要创建一个协程计数器
	// 获取cpu个数
	//npmCpu := runtime.NumCPU()
	//fmt.Println("cup的个数:", npmCpu)
	//// 设置允许使用的CPU数量
	//runtime.GOMAXPROCS(runtime.NumCPU() - 1)
	//	尝试利用循环测试开启多个协程
	//for i := 1; i < 10; i++ {
	//	go test5(i)
	//	wg.Add(1)
	//}
	//wg.Wait()
	//fmt.Println("主线程结束")

	//Channel管道
	//管道是Golang在语言级别上提供的goroutine间的通讯方式，
	//我们可以使用channel在多个goroutine之间传递消息。
	//如果说goroutine是Go程序并发的执行体，
	//channel就是它们之间的连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
	//
	//Golang的并发模型是CSP（Communicating Sequential Processes），
	//提倡通过通信共享内存而不是通过共享内存而实现通信。
	//
	//Go语言中的管道（channel）是一种特殊的类型。
	//管道像一个传送带或者队列，总是遵循先入先出（First In First Out）的规则，
	//保证收发数据的顺序。每一个管道都是一个具体类型的导管，也就是声明channel的时候需要为其指定元素类型。
	//channel类型
	//channel是一种类型，一种引用类型。声明管道类型的格式如下：

	// 声明一个传递整型的管道
	//var ch1 chan int
	//// 声明一个传递布尔类型的管道
	//var ch2 chan bool
	//// 声明一个传递int切片的管道
	//var ch3 chan []int

	//创建channel
	//声明管道后，需要使用make函数初始化之后才能使用
	//
	//make(chan 元素类型, 容量)
	//举例如下：
	//
	//// 创建一个能存储10个int类型的数据管道
	//ch1 = make(chan int, 10)
	//// 创建一个能存储4个bool类型的数据管道
	//ch2 = make(chan bool, 4)
	//// 创建一个能存储3个[]int切片类型的管道
	//ch3 = make(chan []int, 3)

	//channel操作
	//管道有发送，接收和关闭的三个功能
	//
	//发送和接收 都使用 <- 符号
	//
	//现在我们先使用以下语句定义一个管道：

	//ch1 := make(chan int, 10)
	////	向chan中放入一个数据
	//ch1 <- 1
	////	取出管道中的值
	//chVal := <-ch1
	//fmt.Println(chVal)

	//关闭管道.
	//	通过调用内置的close函数来关闭管道
	//
	//close(ch)
	// 创建管道
	//ch := make(chan int, 10)
	//
	//// 给管道里面存储数据
	//ch <- 10
	//ch <- 21
	//ch <- 32
	//ch <- 33
	//ch <- 34
	//ch <- 35
	//close(ch)
	//for value := range ch {
	//	fmt.Println(value)
	//}
	//for i := 0; i < 10; i++ {
	//	fmt.Println(<-ch)
	//}
	//上述代码没有报错，说明通过for i的循环方式，可以不关闭管道

	// 获取管道里面的内容
	//a := <-ch
	//fmt.Println("打印出管道的值：", a)
	//fmt.Println("打印出管道的值：", <-ch)
	//fmt.Println("打印出管道的值：", <-ch)
	//
	//// 管道的值、容量、长度
	//fmt.Printf("地址：%v 容量：%v 长度：%v \n", ch, cap(ch), len(ch))
	//
	//// 管道的类型
	//fmt.Printf("%T \n", ch)
	// 管道阻塞（当没有数据的时候取，会出现阻塞，同时当管道满了，继续存也会）
	//<-ch // 没有数据取，出现阻塞
	//fatal error: all goroutines are asleep - deadlock!
	//
	//	goroutine 1 [chan receive]:
	//ch <- 10
	//ch <- 10
	//ch <- 10
	//ch <- 10 // 管道满了，继续存，也出现阻塞
	//fatal error: all goroutines are asleep - deadlock!
	//
	//	goroutine 1 [chan send]:
	//Goroutine 结合 channel 管道
	//需求：定义两个方法，一个方法给管道里面写数据，一个给管道里面读取数据。要求同步进行。
	//
	//开启一个fn1的的协程给向管道inChan中写入00条数据
	//开启一个fn2的协程读取inChan中写入的数据
	//注意：fn1和fn2同时操作一个管道
	//主线程必须等待操作完成后才可以退出
	//ch := make(chan int)
	//wg.Add(1)
	//go WirteChannel(ch)
	//wg.Add(1)
	//go ReadChannel(ch)
	//wg.Wait()
	//fmt.Println("主程序读完了")
	//管道是安全的，是一边写入，一边读取，当读取比较快的时候，会等待写入

	//单向管道
	//有时候我们会将管道作为参数在多个任务函数间传递，很多时候我们在不同的任务函数中，使用管道都会对其进行限制，比如限制管道在函数中只能发送或者只能接受
	//
	//默认的管道是 可读可写
	//
	//// 定义一种可读可写的管道
	//var ch = make(chan int, 2)
	//ch <- 10
	//<- ch
	//
	//// 管道声明为只写管道，只能够写入，不能读
	//var ch2 = make(chan<- int, 2)
	//ch2 <- 10
	//
	//// 声明一个只读管道
	//var ch3 = make(<-chan int, 2)
	//<- ch3
	//intChan := make(chan int, 10)
	//intChan <- 10
	//intChan <- 12
	//intChan <- 13
	//stringChan := make(chan int, 10)
	//stringChan <- 20
	//stringChan <- 23
	//stringChan <- 24
	//
	//// 每次循环的时候，会随机中一个chan中读取，其中for是死循环
	//for {
	//	select {
	//	case v := <-intChan:
	//		fmt.Println("从initChan中读取数据：", v)
	//	case v := <-stringChan:
	//		fmt.Println("从stringChan中读取数据：", v)
	//	default:
	//		fmt.Println("所有的数据获取完毕")
	//		close(intChan)
	//		close(stringChan)
	//		return
	//	}
	//}
	//tip：使用select来获取数据的时候，不需要关闭chan，不然会出现问题
	//go sayHello()
	//go errTest()

	//互斥锁
	//互斥锁是传统并发编程中对共享资源进行访问控制的主要手段，它由标准库sync中的Mutex结构体类型表示。sync.Mutex类型只有两个公开的指针方法，Lock和Unlock。Lock锁定当前的共享资源，Unlock 进行解锁
	//
	//// 定义一个锁
	//var mutex sync.Mutex
	//// 加锁
	//mutex.Lock()
	//// 解锁
	//mutex.Unlock()
	for i := 0; i < 20; i++ {
		wg.Add(1)
		go test4()
	}
	wg.Wait()
}

var mutex sync.Mutex

var count = 0

func test4() {
	mutex.Lock()
	count++
	fmt.Println("the count is : ", count)
	time.Sleep(time.Millisecond)
	wg.Done()
	mutex.Unlock()
}

func sayHello() {
	for i := 0; i < 10; i++ {
		fmt.Println("hello")
	}
}
func errTest() {
	// 捕获异常
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("errTest发生错误")
		}
	}()
	var myMap map[int]string
	myMap[0] = "10"
}

var wg sync.WaitGroup

// WirteChannel 创建一个函数循环写入管道的值
func WirteChannel(c chan int) {
	for i := 1; i <= 10; i++ {
		fmt.Println("向管道中写入数据", i)
		c <- i
		time.Sleep(time.Millisecond * 50)
	}
	close(c)
	wg.Done()
}

// ReadChannel 创建一个函数循环写入管道的值
func ReadChannel(c chan int) {
	for i := 1; i <= 20; i++ {
		fmt.Println("读出管道的数据", <-c)
		time.Sleep(time.Millisecond * 50)
	}
	wg.Done()
}

func test5(routine int) {
	for i := 1; i < 10; i++ {
		fmt.Printf("协程（%v）打印的第%v条数据 \n", routine, i)
	}
	wg.Done()
}

// 我们创建一个主线程中开启的携程
func test() {
	for i := 0; i < 100; i++ {
		fmt.Println("test 协程输出的 你好golang")
		time.Sleep(50 * time.Millisecond)
	}
}
func test2() {
	for i := 0; i < 30; i++ {
		fmt.Println("test2 协程输出的 你好golang")
		time.Sleep(50 * time.Millisecond)
	}
	wg.Done()
}
func test3() {
	for i := 0; i < 50; i++ {
		fmt.Println("test3 协程输出的 你好golang")
		time.Sleep(50 * time.Millisecond)
	}
	wg.Done()
}
